package utilities;

import java.util.List;
import java.util.Vector;

import org.jgap.Chromosome;
import org.jgap.Population;
import org.jgap.Gene;

public abstract class DominanceUtil { 

	/**
	 * Update the tentative set of pareto-optimal solutions checking one by one
	 * if each of the individuals in the specified population dominates or is 
	 * non-dominated by those in the tentative set.
	 * 
	 * @param population Population : The population to check
	 * @param eliteGroup Vector<Chromosome> : The elite group
	 * @param eliteMax int : The maximum size of the elite
	 * @param eliteProp double : Proportion for diversity
	 */
	public static void UpdateElite(Population population, Vector<Chromosome> eliteGroup, int eliteMax, double eliteProp) {
		
		// For each individual in the population
		for (int i = 0; i < population.size(); i++) {

			// Check if elite
			Chromosome chromo = (Chromosome) population.getChromosome(i);
			if (eliteGroup.size() > 0 && DominanceUtil.CheckElite(chromo, eliteGroup))
				continue;
			
			boolean add = true;
			int maxIndex = eliteGroup.size();
			// Check if the individual is not dominated
			for (int j = 0; j < eliteGroup.size(); j++) {
				
				Chromosome otherChromo = eliteGroup.get(j); // Elite
				int dominance = DominanceUtil.CheckDominance(chromo, otherChromo);

				// Dominated
				if (dominance == -1) { 
					add = false;
					break; 
				} // Dominates 
				else if (dominance == 1) {
					eliteGroup.remove(j);
					maxIndex--; j--;
				}
			}

			// Not dominated
			if (add == true) {
				
				if (eliteGroup.size() >= eliteMax)
					continue;
				
				if (eliteGroup.size() >= eliteMax*eliteProp) {
				
					// Extract solutions
					double[] cand = new double[2];
					cand[0] = (Double) chromo.getMultiObjectives().get(0);
					cand[1] = (Double) chromo.getMultiObjectives().get(1);
					double[][] elite = new double[eliteGroup.size()][2];
					PerformanceUtil.extractSolutions(eliteGroup, elite);
					
					// Calculate minimum
					double minDist = Double.MAX_VALUE; // Initial
					for (int j = 0; j < eliteGroup.size(); j++) {
						double dist = PerformanceUtil.distance(cand, elite[j], 1);
						if (dist < minDist) minDist = dist;
					}
					
					// Calculate diversity measure
					double[] diversity = PerformanceUtil.diversityMeasure(elite);
					double mean = diversity[0];	double diff = minDist - mean;
					
					double prob  = 0;
					if (diff > 0) prob = 1;
					
					// Ensure probability
					if (prob > 1) prob = 1;
					else if (prob < 0) prob = 0;
					if (Math.random() > prob)
						continue;
				}
				
				Chromosome newChromo = (Chromosome) chromo.clone();
				newChromo.getFitnessValue(); // Calculate
				newChromo.setAge(chromo.getAge());
				eliteGroup.add(newChromo);
				
			}
			
		}
		
	}
	
	/**
	 * Update the content of the population randomly extracting a given number of regular 
	 * individuals and introducing the same number of non-dominated solutions to the population.
	 * 
	 * @param population Population : The population to change
	 * @param eliteGroup Vector<Chromosome> : The elite group
	 * @param eliteNum int : The number of chromosomes
	 */
	public static void UpdatePopulation(Population population, Vector<Chromosome> eliteGroup, int eliteNum) {
		
		// Remove regular individuals
		for (int i = 0; i < eliteNum; i++) {
			int randomIndex = (int) Math.floor((Math.random() * population.size()));
			population.getChromosomes().remove(randomIndex); // Remove randomly
		}

		// Add non-dominated individuals
		for (int i = 0; i < eliteNum; i++) {
			int randomIndex = (int) Math.floor((Math.random() * eliteGroup.size()));
			Chromosome chromo = eliteGroup.get(randomIndex); // Include randomly
			Chromosome newChromo = (Chromosome) chromo.clone();
			newChromo.setAge(chromo.getAge());
			newChromo.getFitnessValue(); // Calculate
			population.getChromosomes().add(newChromo);
		}

	}
	
	/**
	 * Check if the specified chromosome is already part of the elite by considering 
	 * only the genes of the members in the elite group, since the regular compareTo() 
	 * method does not work that way.
	 * 
	 * @param chromo Chromosome : The chromosome to check
	 * @param eliteGroup Vector<Chromosome> : The elite group
	 * 
	 * @return boolean : True, if it is already in the group
	 */
	public static boolean CheckElite(Chromosome chromo, Vector<Chromosome> eliteGroup) {
		
		// Check each gene repetition
		Gene[] chromoGenes = chromo.getGenes();
		for (int i = 0; i < eliteGroup.size(); i++) {
			Gene[] otherGenes = eliteGroup.get(i).getGenes();
			
			boolean different = false;
			for (int j = 0; j < chromoGenes.length; j++) {
				if (!chromoGenes[j].equals(otherGenes[j])) {
					different = true;
					break;
				}
			}
			
			if (different == false)
				return true;
		}
		
		return false;
	}
	
	/**
	 * Check the dominance relationship between the specified chromosomes. The
	 * method returns -1 if the one is dominated by other, 1 if one dominates
	 * other and 0 if none of this happen.
	 * 
	 * @param one Chromosome : One chromosome
	 * @param other Chromosome : Other chromosome
	 * 
	 * @return int : The relation between both chromosomes
	 */
	@SuppressWarnings("rawtypes")
	public static int CheckDominance(Chromosome one, Chromosome other) {
		boolean dominanceOne = true;
		boolean dominanceOther = true;

		// Calculate fitness
		one.getFitnessValue();
		other.getFitnessValue();
		List objValuesOne = one.getMultiObjectives();
		List objValuesOther = other.getMultiObjectives();
		int objNum = objValuesOne.size();

		// Check dominance
		for (int i = 0; i < objNum; i++) {
			double oneValue = ((Double) objValuesOne.get(i)).doubleValue();
			double otherValue = ((Double) objValuesOther.get(i)).doubleValue();

			if (oneValue > otherValue) 
				dominanceOne = false;
			if (otherValue > oneValue) 
				dominanceOther = false;
		}

		// Return
		if (dominanceOne && dominanceOther)
			return -1; // Same solution
		if (dominanceOne && !dominanceOther)
			return 1; // One dominates
		if (dominanceOther && !dominanceOne)
			return -1; // Other dominates
		return 0;
	}
	
}
